#include <boost/assign/list_of.hpp>
#include <boost/core/null_deleter.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/filesystem/convenience.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/attributes/attribute.hpp>
#include <boost/log/attributes/attribute_cast.hpp>
#include <boost/log/attributes/attribute_value.hpp>
#include <boost/log/attributes/attribute_value_impl.hpp>
#include <boost/log/attributes/constant.hpp>
#include <boost/log/attributes/mutable_constant.hpp>
#include <boost/log/attributes/named_scope.hpp>
#include <boost/log/core.hpp>
#include <boost/log/detail/config.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/expressions/formatters/date_time.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/sinks/text_multifile_backend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/sources/basic_logger.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/sources/severity_feature.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <map>
#include <sstream>
#include <string>
#include <stdio.h>
#include <pwd.h>
#include <unistd.h>

#include "modules/AssistModule/TextLog.h"
#include "data_struct/base/TimeWorkBase/time/InExecution.h"
#include "modules/AssistModule/LogModule.h"
#include "lib/Time.h"

namespace behavior_controller
{

    namespace logging = boost::log;
    namespace trivial = boost::log::trivial;
    namespace attrs = boost::log::attributes;
    namespace src = boost::log::sources;
    namespace sinks = boost::log::sinks;
    namespace expr = boost::log::expressions;
    namespace keywords = boost::log::keywords;

    BOOST_LOG_ATTRIBUTE_KEYWORD(severity_kw, "Severity", LogLevel)
    BOOST_LOG_ATTRIBUTE_KEYWORD(log_name_kw, "Name", std::string)

    class RunningTimeAttrImpl : public logging::attribute::impl
    {
    public:
        RunningTimeAttrImpl() : in_execution(InExecution::Ref()) {}

        // The method generates a new attribute value
        logging::attribute_value get_value()
        {
            return attrs::make_attribute_value(in_execution.GetTimestamp());
        }

    private:
        const InExecution &in_execution;
    };

    class RunningNumberAttrImpl : public logging::attribute::impl
    {
    public:
        RunningNumberAttrImpl() : in_execution(InExecution::Ref()) {}

        // The method generates a new attribute value
        logging::attribute_value get_value()
        {
            return attrs::make_attribute_value(in_execution.GetTimeNumber());
        }

    private:
        const InExecution &in_execution;
    };

    template <typename AttrImpl>
    class CustomAttr : public logging::attribute
    {
    public:
        CustomAttr() : logging::attribute(new AttrImpl()) {}
        // Attribute casting support
        explicit CustomAttr(attrs::cast_source const &source)
            : logging::attribute(source.as<AttrImpl>())
        {
        }
    };

    class Collector : public sinks::file::collector
    {
    public:
        Collector(std::string log_name, std::string source_log_dir,
                  std::string storage_log_dir)
            : counter_(nullptr),
              log_name_(log_name),
              source_log_dir_(source_log_dir),
              storage_log_dir_(storage_log_dir)
        {
        }

        unsigned int *counter() { return counter_; }

    private:
        void store_file(boost::filesystem::path const &src_path) override
        {
            move_file(src_path);
        }

        uintmax_t scan_for_files(sinks::file::scan_method /*method*/,
                                 boost::filesystem::path const & /*pattern*/,
                                 unsigned int *counter) override
        {
            counter_ = counter;
            (*counter) = InExecution::Ref().GetTimeNumber();

            if (storage_log_dir_.empty())
            {
                return (*counter);
            }

            if (!boost::filesystem::exists(source_log_dir_))
            {
                return (*counter);
            }

            boost::filesystem::directory_iterator it(source_log_dir_), end;
            for (; it != end; ++it)
            {
                boost::filesystem::path pth = *it;

                if (!boost::filesystem::is_regular_file(pth))
                {
                    continue;
                }

                if (pth.filename().stem().stem() != log_name_)
                {
                    continue;
                }

                try
                {
                    unsigned int number =
                        std::stoi(pth.extension().string().substr(1));
                    if (number != *counter)
                    {
                        move_file(pth);
                    }
                }
                catch (const std::invalid_argument &e)
                {
                }
            }

            return *counter;
        }

        void move_file(boost::filesystem::path const &src_path)
        {
            const std::string src_path_str = src_path.parent_path().string();
            const std::string tar_path_str = storage_log_dir_.string();

            if (storage_log_dir_.empty())
            {
                return;
            }

            size_t st_idx = 0;
            for (; st_idx < src_path_str.size(); st_idx++)
            {
                if (src_path_str.at(st_idx) != tar_path_str.at(st_idx))
                {
                    break;
                }
            }

            if (st_idx == src_path_str.size())
            {
                return;
            }

            boost::filesystem::path tar_path =
                storage_log_dir_ / src_path.string().substr(st_idx);

            if (!boost::filesystem::exists(tar_path.parent_path()))
            {
                boost::filesystem::create_directories(tar_path.parent_path());
            }
            boost::filesystem::rename(src_path, tar_path);
        }

        unsigned int *counter_;
        std::string log_name_;
        boost::filesystem::path source_log_dir_;
        boost::filesystem::path storage_log_dir_;
    };

    class TextLogInfo
    {
    public:
        typedef sinks::text_file_backend backend_t;
        typedef sinks::synchronous_sink<backend_t> sink_t;
        typedef sinks::synchronous_sink<sinks::text_file_backend> file_sink;

        TextLogInfo(std::string log_name, std::string log_dir,
                    std::string storage_log_dir = "")
            : log_name_(log_name),
              log_dir_(log_dir),
              storage_log_dir_(storage_log_dir),
              log_number_(InExecution::Ref().GetTimeNumber())
        {
            Initialize();
        }

        void Log(LogLevel severity, const std::string &log)
        {
            BOOST_LOG_SEV(logger_, severity) << log;
        }

        void Flush() { sink_ptr_->flush(); }

    private:
        void Initialize()
        {
            auto core = boost::log::core::get();
            std::string usr_name(getenv("HOME"), getenv("HOME") + strlen(getenv("HOME")));
            log_dir_ = usr_name + log_dir_;
            storage_log_dir_ = usr_name + storage_log_dir_;

            backend_ptr_ = boost::make_shared<backend_t>(
                keywords::file_name = log_dir_ + log_name_ + ".log.%N",
                keywords::open_mode = std::ios_base::app,
                keywords::time_based_rotation =
                    [this]() {
                        int number = InExecution::Ref().GetTimeNumber();
                        if (log_number_ != number)
                        {
                            log_number_ = InExecution::Ref().GetTimeNumber();
                            if (collector_ptr_->counter() != nullptr)
                            {
                                (*collector_ptr_->counter()) = log_number_;
                            }
                            return true;
                        }
                        return false;
                    },
                keywords::auto_flush = true);
            std::string file_dir = log_dir_ + log_name_ + ".log.%N";
            sink_ptr_ = boost::make_shared<sink_t>(backend_ptr_);

            collector_ptr_ = boost::make_shared<Collector>(log_name_, log_dir_,
                                                           storage_log_dir_);
            sink_ptr_->locked_backend()->set_file_collector(collector_ptr_);

            sink_ptr_->locked_backend()->scan_for_files();

            sink_ptr_->set_formatter(
                expr::format("[%1%][%2%][%3%]: %4%") %
                expr::attr<LogLevel>("Severity") %
                expr::attr<std::string>("Time") %
                // expr::format_date_time<boost::posix_time::ptime>(
                //     "TimeStamp", "%Y-%m-%d_%H:%M:%S.%f") % //Time::Text()
                expr::attr<unsigned int>("LineID") %
                expr::smessage);

            // sink_ptr_->set_formatter(
            //     expr::format("[%1%][%2%][%3%][%4%][%5%]: %6%") %
            //     expr::attr<int64_t>("RT") % expr::attr<std::string>("Name") %
            //     expr::attr<LogLevel>("Severity") %
            //     expr::format_date_time<boost::posix_time::ptime>(
            //         "TimeStamp", "%Y-%m-%d_%H:%M:%S.%f") %
            //     expr::attr<unsigned int>("LineID") % expr::smessage);

            sink_ptr_->set_filter(expr::has_attr(log_name_kw) &&
                                  log_name_kw == log_name_);
            core->add_sink(sink_ptr_);

            logger_.add_attribute("Name", attrs::constant<std::string>(log_name_));
            logger_.add_attribute("Time", attrs::constant<std::string>(Time::Text()));
            //logger_.add_attribute("RT", CustomAttr<RunningTimeAttrImpl>());
            boost::log::add_common_attributes();
        }

        boost::shared_ptr<backend_t> backend_ptr_;
        boost::shared_ptr<sink_t> sink_ptr_;
        boost::shared_ptr<Collector> collector_ptr_;
        boost::log::sources::severity_logger<LogLevel> logger_;
        std::string log_name_;
        std::string log_dir_;
        std::string storage_log_dir_;
        int32_t log_number_;
    };

    TextLog::TextLog(std::string name, LogLevel lvl,
                     std::string log_dir, std::string storage_log_dir)
        : LogBase(name, "boost", lvl),
          logger_(std::make_shared<TextLogInfo>(name, log_dir,
                                                storage_log_dir))
    {
    }

    void TextLog::Flush() const { logger_->Flush(); }

    void TextLog::ErrorLog(const std::string &log) const
    {
        logger_->Log(LogLevel::error, log);
    }
    void TextLog::WarnLog(const std::string &log) const
    {
        logger_->Log(LogLevel::warn, log);
    }
    void TextLog::InfoLog(const std::string &log) const
    {
        logger_->Log(LogLevel::info, log);
    }
    void TextLog::DebugLog(const std::string &log) const
    {
        logger_->Log(LogLevel::debug, log);
    }

} // namespace behavior_controller